<script lang="ts">
export type { CountUp as ICountUp, CountUpOptions } from 'countup.js'
export default {
    name: 'CountUp'
}
</script>
<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch } from 'vue'
import { CountUp } from 'countup.js'
import type { CountUpOptions } from 'countup.js'

const props = withDefaults(
    defineProps<{
        // 结束数值
        endVal: number | string
        // 开始数值
        startVal?: number | string
        // 动画时长，单位 s
        duration?: number | string
        // 是否自动计数
        autoplay?: boolean
        // 循环次数，有限次数 / 无限循环
        loop?: boolean | number | string
        // 延时，单位 s
        delay?: number
        // countup 配置项
        options?: CountUpOptions
    }>(),
    {
        startVal: 0,
        duration: 2.5,
        autoplay: true,
        loop: false,
        delay: 0,
        options: undefined
    }
)
const emits = defineEmits<{
    // countup init complete
    (event: 'init', countup: CountUp): void
    // count complete
    (event: 'finished'): void
}>()

let elRef = ref<HTMLElement>()
let countUp = ref<CountUp>()

const initCountUp = () => {
    if (!elRef.value) return
    const startVal = Number(props.startVal)
    const endVal = Number(props.endVal)
    const duration = Number(props.duration)
    countUp.value = new CountUp(elRef.value, endVal, {
        startVal,
        duration,
        ...props.options
    })
    if (countUp.value.error) {
        console.error(countUp.value.error)
        return
    }
    emits('init', countUp.value)
}

const startAnim = (cb?: () => void) => {
    countUp.value?.start(cb)
}

// endVal change & autoplay: true, restart animate
watch(
    () => props.endVal,
    (value) => {
        if (props.autoplay) {
            countUp.value?.update(value)
        }
    }
)

// loop animation
const finished = ref(false)
let loopCount = 0
const loopAnim = () => {
    loopCount++
    startAnim(() => {
        const isTruely = typeof props.loop === 'boolean' && props.loop
        if (isTruely || (typeof props.loop == 'number' && props.loop > loopCount)) {
            delay(() => {
                countUp.value?.reset()
                loopAnim()
            }, props.delay)
        } else {
            finished.value = true
        }
    })
}
watch(finished, (flag) => {
    if (flag) {
        emits('finished')
    }
})

onMounted(() => {
    initCountUp()
    if (props.autoplay) {
        loopAnim()
    }
})
onUnmounted(() => {
    cancelAnimationFrame(dalayRafId)
    countUp.value?.reset()
})

let dalayRafId: number
// delay to execute callback function
const delay = (cb: () => unknown, seconds = 1) => {
    let startTime: number
    function count(timestamp: number) {
        if (!startTime) startTime = timestamp
        const diff = timestamp - startTime
        if (diff < seconds * 1000) {
            dalayRafId = requestAnimationFrame(count)
        } else {
            cb()
        }
    }
    dalayRafId = requestAnimationFrame(count)
}

const restart = () => {
    initCountUp()
    startAnim()
}

defineExpose({
    init: initCountUp,
    restart
})
</script>

<template>
    <div class="countup-wrap">
        <slot name="prefix"></slot>
        <span ref="elRef"> </span>
        <slot name="suffix"></slot>
    </div>
</template>
<style lang="scss" scoped>
.countup-wrap{
  text-align: center;
}
</style>